package org.bookshare.validator.rule;

import java.util.List;
import java.util.Map;

import org.benetech.beans.error.ValidationError.Severity;
import org.benetech.collections.ListMap;
import org.benetech.event.EventListener;
import org.benetech.util.XMLParseUtils;
import org.bookshare.document.beans.DocumentSet;
import org.bookshare.document.beans.HasOPF;
import org.bookshare.validator.report.ValidationMetadata;
import org.bookshare.validator.util.XMLValidationUtils;
import org.jdom.Document;
import org.jdom.Element;

/**
 * Validate that the metadata within the OPF meets minimal standards.
 * @author Reuben Firmin
 */
public final class OPFMetadataRule implements Rule {

    // TODO, construct test cases for this stuff

	/**
	 * Default constructor.
	 */
	public OPFMetadataRule() {
		// empty
	}

    /**
     * {@inheritDoc}
     */
    public void evaluate(final ValidationMetadata reportMetadata, final EventListener listener)
    {
        final DocumentSet docSet = reportMetadata.getDocumentSet();

        final Document doc = ((HasOPF) docSet).getOPFDocument();
        listener.message("Checking " + doc.getBaseURI() + " for required metadata elements");
        final Element root = doc.getRootElement();

        final Element metadata = XMLValidationUtils.assertExistsAsExpected(root, "metadata", root.getNamespace(),
        		reportMetadata, Severity.ERROR, doc);

        if (metadata != null) {
            processDublinCoreMetadata(metadata, reportMetadata, root, doc);
            processExtendedMetadata(metadata, reportMetadata, root, doc);
        }
    }

    /**
     * Process the dublin core metadata.
     * @param metadata The metadata element.
     * @param reportMetadata The list of reportMetadata to add to.
     * @param root The root element in the document.
     * @param doc The document being parsed.
     */
    private void processDublinCoreMetadata(final Element metadata,
            final ValidationMetadata reportMetadata, final Element root,
            final Document doc)
    {
    	final Element dcmetadata = XMLValidationUtils.assertExistsAsExpected(metadata, "dc-metadata",
    			root.getNamespace(), reportMetadata, Severity.ERROR, doc);
        final ListMap<String, Element> dcMetadataMap = new ListMap<String, Element>();

        if (dcmetadata != null) {
        	final Map<String, List<Element>> childMap = (Map<String, List<Element>>)
                XMLParseUtils.getChildrenAsMap(dcmetadata);
            // TODO, may want to verify their content
            dcMetadataMap.putAll(
                    "title", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Title",
                    reportMetadata, Severity.ERROR, doc, false));
            dcMetadataMap.putAll(
                    "author", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Creator",
                    "role", "author", reportMetadata,
                    Severity.ERROR, doc, true));
            dcMetadataMap.putAll(
                    "editor", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Creator",
                    "role", "editor", reportMetadata,
                    Severity.ERROR, doc, true));
            dcMetadataMap.putAll(
                    "illustrator", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Creator",
                    "role", "illustrator", reportMetadata,
                    Severity.ERROR, doc, true));
            dcMetadataMap.putAll(
                    "publisher", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Publisher",
                    reportMetadata, Severity.WARNING, doc, false));
            dcMetadataMap.putAll(
                    "createdDate", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Date",
                    "event", "DCTERMS.created", reportMetadata,
                    Severity.ERROR, doc, true));
            dcMetadataMap.putAll(
                    "format", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Format",
                    reportMetadata, Severity.WARNING, doc, false));
            dcMetadataMap.putAll(
                    "identifier", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Identifier",
                    reportMetadata, Severity.WARNING, doc, false));
            dcMetadataMap.putAll(
                    "language", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Language",
                    reportMetadata, Severity.WARNING, doc, true));
            dcMetadataMap.putAll(
                    "rights", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Rights",
                    reportMetadata, Severity.WARNING, doc, false));
            dcMetadataMap.putAll(
                    "isbn", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Source",
                    reportMetadata, Severity.WARNING, doc, true));
            dcMetadataMap.putAll(
                    "subject", XMLValidationUtils.assertExistsAsExpected(
                    dcmetadata.getName(), childMap, "Subject",
                    reportMetadata, Severity.WARNING, doc, true));
        }
        reportMetadata.getModelMap().put("dcMetadata", dcMetadataMap);
    }

    /**
     * Process the extended metadata.
     * @param metadata The metadata element.
     * @param reportMetadata The list of reportMetadata to add to.
     * @param root The root element in the document.
     * @param doc The document being parsed.
     */
    private void processExtendedMetadata(final Element metadata,
        final ValidationMetadata reportMetadata, final Element root,
        final Document doc)
    {
    	final Element xmetadata = XMLValidationUtils.assertExistsAsExpected(metadata, "x-metadata", root.getNamespace(),
                reportMetadata, Severity.ERROR, doc);
    	final ListMap<String, Element> xMetadataMap = new ListMap<String, Element>();

        if (xmetadata != null) {
        	final Map<String, List<Element>> childMap =
                (Map<String, List<Element>>) XMLParseUtils.getChildrenAsMap(xmetadata);
            xMetadataMap.putAll(
                    "publisherPlace", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.publisher.place", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "sourceEdition", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "nimas-SourceEdition", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "descriptionVersion",
                    XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.description.version", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "sourceDate", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "nimas-SourceDate", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "dateIssues", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.date.issued", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "dateCopyrighted",
                    XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.date.dateCopyrighted", reportMetadata,
                    Severity.ERROR, doc, false));
            xMetadataMap.putAll(
                    "relationPartOf", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.relation.isPartOf", reportMetadata,
                    Severity.ERROR, doc, true));
            // XXX, this field is overloaded! how can we distinguish?
            xMetadataMap.putAll(
                    "descriptionNote",
                    XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.description.note", reportMetadata,
                    Severity.ERROR, doc, true));
            // -------- OPTIONAL
            xMetadataMap.putAll(
                    "formatExtent", XMLValidationUtils.assertExistsAsExpected(
                    xmetadata.getName(), childMap, "meta", "name",
                    "DCTERMS.format.extent", reportMetadata,
                    Severity.INFO, doc, false));
        }
        reportMetadata.getModelMap().put("xMetadata", xMetadataMap);
    }
}
